
"use strict";

export class token_type {
    constructor(name, str = "") {
        this.name = name;
        this.str = str;
    }
}

export const token_types = {
    // basic chars
    left_bracket: new token_type("left_bracket", "("),
    right_bracket: new token_type("right_bracket", ")"),
    sharp: new token_type("sharp", "#"),
    semicolon: new token_type("semicolon", ";"),
    double_quote: new token_type("double_quote", "\""),
    single_quote: new token_type("single_quote", "'"),
    back_quote: new token_type("back_quote", "`"),
    at: new token_type("at", "@"),
    comma: new token_type("comma", ","),
    space: new token_type("space", " "),
    tab: new token_type("tab", "\t"),
    backslash: new token_type("backslash", "\\"),
    newline: new token_type("newline", "\n"),
    carriagereturn: new token_type("carriagereturn", "\r"),
    // unknown/normal
    normal: new token_type("normal", null),
    // functional unit
    string: new token_type("string", null),
    comment: new token_type("comment", null),
    left_double_quote: new token_type("left_double_quote", "\""),
    right_double_quote: new token_type("right_double_quote", "\""),
};

export class token {
    /**
     * 
     * @param {string} type in token_types
     * @param {string} str 
     * @param {int} line 
     * @param {int} column 
     */
    constructor(type, str, line, column) {
        this.type = type;
        this.str = str;
        this.line = line;
        this.column = column;
    }
}

function remove_firstline_shebang(s) {
    let s_len = s.length;
    if (s_len == 0) { return s; }

    let first_newline = s.indexOf("\n")
    if (first_newline != -1 && s.startsWith("#!")) {
        if (first_newline == (s_len - 1)) {
            return s;
        } else {
            return s.substring(first_newline + 1);
        }
    }
    else {
        return s;
    }
}

/**
 * 
 * @param {string} s 
 * @param {int} start_line 
 * @returns {token[]}
 */
function split_by_alphabeta(s, start_line = 0, start_column = 0) {
    let tokens = [];
    let line_number = start_line;
    let column_number = start_column;

    for (let i of s) {
        let pushed_flag = false;
        for (let j in token_types) {
            if (i == token_types[j].str) {
                tokens.push(new token(token_types[j], i, line_number, column_number));
                pushed_flag = true;
                break;
            }
        }
        if (!pushed_flag) {
            tokens.push(new token(token_types.normal, i, line_number, column_number));
        }

        column_number += 1;

        if (tokens[tokens.length - 1].type == token_types.newline || tokens[tokens.length - 1].type == token_types.carriagereturn) {
            // \n\r
            if (tokens[tokens.length - 1].type == 'newline' && tokens[tokens.length - 2].type == 'carriagereturn') {
                tokens[tokens.length - 1].line -= 1;
                tokens[tokens.length - 1].column = tokens[tokens.length - 2].column + 1;
                column_number = 0;
            }
            // \r\n
            else if (tokens[tokens.length - 1].type == token_types.carriagereturn && tokens[tokens.length - 2].type == token_types.newline) {
                tokens[tokens.length - 1].line -= 1;
                tokens[tokens.length - 1].column = tokens[tokens.length - 2].column + 1;
                column_number = 0
            }
            else { // single \n or \r
                line_number += 1;
                column_number = 0;
            }
        }
    }

    return tokens;
}


/**
 * 
 * @param {string} s source code
 * @returns {token[]}
 */
export function tokenize(s) {
    if (typeof (s) != "string") {
        console.error("Input is not a string!");
        return -1;
    }

    let tokens = [];

    let line_number = 0;
    let column_number = 0;

    let s_len = s.length;
    s = remove_firstline_shebang(s);
    if (s_len > s.length) {
        s_len = s.length;
        line_number += 1;
    }

    tokens = split_by_alphabeta(s, line_number, column_number);

    return tokens;
}


